package org.metasketch.simulator.runtime;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.metasketch.logger.Log;
import org.metasketch.simulator.configuration.loaders.SimpleTransaction;
import org.metasketch.simulator.petri.Link;
import org.metasketch.simulator.petri.PetriNet;
import org.metasketch.simulator.petri.PetriNetNode;
import org.metasketch.simulator.petri.PetriNetProxy;
import org.metasketch.simulator.petri.Place;
import org.metasketch.simulator.petri.Token;
import org.metasketch.simulator.petri.Transition;
import org.metasketch.simulator.resources.Resource;
import org.metasketch.simulator.resources.ResourceBase;
import org.metasketch.simulator.resources.ResourceRequirements;

public class PetriNetTraversal {

	ResourceResolver resourceResolver;
	SimulationProcess process;
	PetriNet petriNet;
	PetriNetProxy proxy;
	PetriNetTraversal parent;
	// PetriNetNode currentNode;

	List<Token> tokens = new ArrayList<Token>();
	List<PetriNetTraversal> traversals = new ArrayList<PetriNetTraversal>();

	public PetriNetTraversal(PetriNet initialPetriNet,
			SimulationProcess simulationProcess) {
		this.petriNet = initialPetriNet;
		this.process = simulationProcess;
		this.resourceResolver = simulationProcess.getProcessManager()
				.getResourceManager();

		Token token = new Token();
		token.setId(process.getId());
		token.setCurrentNode(petriNet.getStart());

		tokens.add(token);

	}

	public void simulate(Long currentTime) throws Exception {

		boolean change = false;
		do {
			change = false;
			// process for execution of transitions and propagation of tokens...
			// TODO need to put something in the case of a PetriNetProxy...
			List<Token> processingTokens = new ArrayList<Token>();
			processingTokens.addAll(tokens);

			for (Token token : processingTokens) {

				// there will be a recursive function here...

				PetriNetNode node = token.getCurrentNode();

				if (node instanceof Transition) {

					// still executing...do nothing, continue
					if (token.getFinishExecution() != null
							&& token.getFinishExecution()
									.compareTo(currentTime) > 0)
						continue;
					else {
						// TODO need to test outputs to see if it can go to a
						// place...
						boolean okToProceed = true;
						// Token currentToken = token;
						List<Link> outputs = node.getOutputs();
						for (Link link : outputs) {
							PetriNetNode target = link.getTarget();
							List<Link> inputs = target.getInputs();
							for (Link input : inputs) {
								PetriNetNode source = input.getSource();

								Token inToken = getToken(source);
								if (inToken == null) {
									okToProceed = false;
									break;
								}

							}
						}

						if (!okToProceed)
							continue;

						change = true;
						// execution is over, so...
						// Log.println("Finished: " + currentTime + " - "
						// + ((Transition) node).getName() + " ("
						// + token.getId() + ")");
						// reset the timer...
						token.setFinishExecution(null);

						Log.println("Inside transition " + node.getName()
								+ " (" + process.getId() + ")");

						releaseResources();

						Log.println("Outside transition " + node.getName()
								+ " (" + process.getId() + ")");

						// put tokens in every output link...
						Token currentToken = token;
						outputs = node.getOutputs();

						if (outputs == null || outputs.size() == 0) {
							Log.println("Finished process " + process.getId());
							process.terminate();
						}

						for (Link link : outputs) {
							if (currentToken == null) {
								currentToken = new Token();
								currentToken.setId(process.getId());
								// TODO need to add to tokens list..no
								// concurrent
								// modification though...hmmm
								if (!tokens.contains(currentToken))
									tokens.add(currentToken);
							}
							PetriNetNode target = link.getTarget();
							currentToken.setCurrentNode(target);
							currentToken = null;
						}

						// recursive ???? or not ???

					}
				}

			}

			// now process for movement...
			processingTokens = new ArrayList<Token>();
			processingTokens.addAll(tokens);

			for (Token token : processingTokens) {

				PetriNetNode node = token.getCurrentNode();

				if (node instanceof Transition) {

					// still executing...do nothing, continue
					if (token.getFinishExecution() != null
							&& token.getFinishExecution()
									.compareTo(currentTime) > 0)
						continue;
				} else if (node instanceof PetriNetProxy) {

					PetriNetProxy proxy = (PetriNetProxy) node;

					PetriNetTraversal traversal = new PetriNetTraversal(
							proxy.getNet(), process);
					traversal.setProxy(proxy);
					traversal.setParent(this);
					traversals.add(traversal);

					tokens.remove(token);
					change = true;

					// Log.println("Entering Petri Net Proxy: "
					// + proxy.getNet().getName() + " (" + process.getId()
					// + ")");

					continue;

				}

				// test for sequence...
				boolean allNodesOkToProceed = true;
				List<Link> outputs = node.getOutputs();
				if (outputs == null || outputs.size() == 0) {
					tokens.remove(token);
					allNodesOkToProceed = false;

					// TODO what to do if in petri net proxy and just finished
					// it...
					if (proxy != null && parent != null) {
						// remove child traversal
						parent.getTraversals().remove(this);
						// create tokens for all outputs from this petri net
						// proxy
						List<Link> outProxy = proxy.getOutputs();
						for (Link link : outProxy) {

							PetriNetNode outProxyTarget = link.getTarget();

							Token currentToken = new Token();
							currentToken.setId(process.getId());
							currentToken.setCurrentNode(outProxyTarget);

							parent.getTokens().add(currentToken);
						}

						this.parent = null;
						// Log.println("Exiting Petri Net Proxy: "
						// + petriNet.getName() + " (" + process.getId()
						// + ")");
						change = true;
					} else
					// if(outputs==null || outputs.size()==0)
					{
						// Log.println("Terminated process " + process.getId());
						process.terminate();
						change = true;
					}
				}

				for (Link link : outputs) {
					PetriNetNode target = link.getTarget();

					// if target is Place, then test for inputs...
					boolean okToProceed = true;

					if (target instanceof Transition) {
						Transition transition = (Transition) target;
						// TODO this will change for channel requirements...
						ResourceRequirements reqs = transition
								.getRequirements().getDefaultRequirements();
						if (!areRequirementsSatisfiable(reqs)) {
							okToProceed = false;
							break;
						}
					}

					List<Link> inputs = target.getInputs();
					for (Link input : inputs) {
						PetriNetNode source = input.getSource();

						Token inToken = getToken(source);
						if (inToken == null) {
							okToProceed = false;
							break;
						}

					}

					if (!okToProceed) {
						allNodesOkToProceed = false;
						break;
					}
				}

				if (!allNodesOkToProceed)
					continue;

				change = true;

				for (Link link : outputs) {
					PetriNetNode target = link.getTarget();
					List<Link> inputs = target.getInputs();
					// cleanup all input tokens
					for (Link input : inputs) {
						Token inToken = getToken(input.getSource());
						tokens.remove(inToken);
					}

					// use only one token and set to this target node
					Token currentToken = new Token();
					currentToken.setId(process.getId());
					currentToken.setCurrentNode(target);
					tokens.add(currentToken);

					if (target instanceof Place) {
						// do nothing...just move on
						// token.setCurrentNode(target);
					} else if (target instanceof Transition) {
						Transition transition = (Transition) target;
						// TODO this will change for channel requirements...
						ResourceRequirements reqs = transition
								.getRequirements().getDefaultRequirements();
						if (areRequirementsSatisfiable(reqs)) {
							Log.println("Inside transition "
									+ transition.getName() + " ("
									+ process.getId() + ")");
							assignRequirements(reqs);
							Log.println("Outside transition "
									+ transition.getName() + " ("
									+ process.getId() + ")");

							// calculate stop time
							setupExecution(transition, currentToken,
									currentTime);

							// token.setCurrentNode(target);
							// TODO recursive here....??? just in case execution
							// time is zero ?
							// Log.println("Starting transition: " + currentTime
							// + " - " + transition.getName() + " ("
							// + token.getId() + ")");
						} else {
							// Log.println("Waiting for resources... ("
							// + process.getId() + ")");
							// do nothing...can't proceed until enough resources
							// exist...
							// throw new Exception(
							// "Can't proceed with transition without satisfying all of the resource requirements !!!");
						}
					}

				}

				// TODO recursive here ???

			}

		} while (change);

		List<PetriNetTraversal> tts = new ArrayList<PetriNetTraversal>();
		tts.addAll(traversals);
		for (PetriNetTraversal traversal : tts) {
			traversal.simulate(currentTime);
		}

	}

	

	private void setupExecution(Transition transition, Token token,
			Long currentTime) {
		// TODO....this will add some time to execute...only hard codedd default
		// value for now...5 mns...
		// if(transition instanceof SimpleTransaction )
		token.setFinishExecution(currentTime + 5 * 60);

	}

	private Token getToken(PetriNetNode source) {
		for (Token token : tokens) {
			if (token.getCurrentNode() == source)
				return token;
		}

		return null;

	}

	
	

	public SimulationProcess getProcess() {
		return process;
	}

	public void setProcess(SimulationProcess process) {
		this.process = process;
	}

	public PetriNet getPetriNet() {
		return petriNet;
	}

	public void setPetriNet(PetriNet pretiNet) {
		this.petriNet = pretiNet;
	}

	public ResourceManager getResourceManager() {
		return resourceManager;
	}

	public void setResourceManager(ResourceManager resourceManager) {
		this.resourceManager = resourceManager;
	}

	public PetriNetProxy getProxy() {
		return proxy;
	}

	public void setProxy(PetriNetProxy proxy) {
		this.proxy = proxy;
	}

	public List<Token> getTokens() {
		return tokens;
	}

	public void setTokens(List<Token> tokens) {
		this.tokens = tokens;
	}

	public PetriNetTraversal getParent() {
		return parent;
	}

	public void setParent(PetriNetTraversal parent) {
		this.parent = parent;
	}

	public List<PetriNetTraversal> getTraversals() {
		return traversals;
	}

	public void setTraversals(List<PetriNetTraversal> traversals) {
		this.traversals = traversals;
	}

}
